%% remotetrans.erl
%%      Generates such helper functions in classes,
%%      which support remote usage of objects.
%%      By remote, I mean that the state of the object is
%%      in a process, and method calls and field manipulation
%%      can only be invoked by message sending.
%%
%%      The functions of this module are invoked by gen_proxy for 
%%      each proxy module.
%% 
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(remotetrans).

%% gen_rnew/2: 
%%     Creates the rnew/0 function for a class in the module of the class.
%%     The rnew() function creates a process which runs the loop of rserver().
%% gen_rserver/2:
%%     Creates the rserver/2 function for a class which is the loop started by rnew().
%%     It keeps the state of an object instance, receives field query/update and method
%%     call messages, and responds to them.
-export([gen_rnew/2, gen_rserver/2]).

-include("common_defs.hrl").

%% atom used in replies sent back to the client
-define(ECT_RESULT, ect_result).


%% Function: gen_rnew(CompileInfo, Line)
%% Purpose: creates AST for the code of a function, that creates a new remote object instance for a class
%%          (This is the rnew/2 function, that is added to the proxy modules of each class.)
%% Arguments: see gen_rserver/2
%% Returns:
%%      The AST for the rnew/2 function.
gen_rnew(#compile_info{class_definition = #class{name = ThisClass}}, Line) ->
    {function, Line, rnew, 0,
      [{clause, Line,
        [],
        [],
        [ast:call(
            spawn,
            [{atom, Line, ThisClass},
             {atom, Line, rserver},
             ast:list([ast:rcall(ThisClass, new, [], Line)], Line)
            ],
            Line)
        ]
       }]
     }.

%% Function: gen_rserver(CompileInfo, Line)
%% Purpose:
%%     Creates the rserver/2 function for a class which is the loop started by rnew().
%%     It keeps the an object instance as a loop variable, and receives field query/update 
%%     and method call messages, and executes the requests on the object instance, and 
%%     sends back responses. The basic structure of rserver/2 is a receive 
%%     statement with clauses for each possible message. It is also a tail-recursive loop.
%% Arguments:
%%     CompileInfo::#compile_info{} - basic information about the compiled module
%%     Line::integer() - line number to use in AST nodes of the source code
%% Returns:
%%     The AST fragment representing the rserver/2 function for the class defined in CompileInfo.
gen_rserver(CompileInfo = #compile_info{class_definition = ClassDefinition}, Line) ->
    RClauses1 = getset_clauses(ClassDefinition, Line),
    RClauses2 =
        [gen_query_call(CompileInfo, Line)
        ,gen_update_call(CompileInfo, Line)
        ,gen_delete(CompileInfo, Line)
        ,gen_copy(CompileInfo, Line)
        |RClauses1],
    {function, Line, rserver, 1,
      [{clause, Line,
        [{var, Line, 'This'}],
        [],
        [{'receive', Line, RClauses2}]
      }]
    }.

%% Function: continue_loop(ClassName, Line)
%% Purpose: Creates an AST fragment of a code that is the (tail-recursive) call of rserver/2
%%          passing the stored object unmodified to the next iteration.
%% Arguments:
%%   ClassName: name of class for which rserver/2 is generated
%%   Line::integer() - line number to use in AST nodes of the source code
%% Returns:
%%   The AST fragment of the recursive call.
continue_loop(ClassName, Line) ->
    ast:rcall(ClassName, rserver, [{var, Line, 'This'}], Line).

%% Function: update_loop(ClassName, Line)
%% Purpose: Creates an AST fragment of a code that is the (tail-recursive) call of rserver/2
%%          passing a new instance as the stored object to the next iteration.
%% Arguments:
%%   ClassName: name of class for which rserver/2 is generated
%%   ThisExpr: an AST fragment that represents the new(modified) instance of the stored object
%%   Line::integer() - line number to use in AST nodes of the source code
%% Returns:
%%   The AST fragment of the recursive call.
update_loop(ClassName, ThisExpr, Line) ->
    ast:rcall(ClassName, rserver, [ThisExpr], Line).

%% Function: send_result(Result, Line)
%% Purpose: Creates an AST fragment of a code that sends back a result to a
%%          caller of this remote object. The code fragment will only work
%%          in rserver/2, because it uses some variables defined there.
%%          This is used when replying to client requests.
%% Arguments:
%%   Result: the value to send back
%%   Line::integer() - line number to use in AST nodes of the source code
%% Returns:
%%   The AST of the code fragment for sending back results to the clients.
send_result(Res1, Line) ->
    Res2 = 
        case Res1 of
        {ok, R} -> {tuple, Line, [{atom, Line, ok}, R]};
        none -> {atom, Line, none}
        end,
    {op, Line, '!', 
        {var, Line, 'Pid'},
        {tuple, Line,
            [{atom, Line, ?ECT_RESULT}, {var, Line, 'Ref'}, Res2]
        }
    }.


%% Function: gen_copy(CompileInfo, Line)
%% Purpose: 
%%      Creates a clause for rserver/2, that handles the copy-commands sent to the object.
%% Arguments: see gen_rserver/2
%% Returns: the AST fragment representing the clause of receive statement that handles the copy commend
%% Processed message: {copy, Pid, Ref}
%% Response: {?ECT_RESULT, Ref, Copy} where
%%           Copy is a copy of the internal object, in data-based form. (tuple structure, ...)
gen_copy(#compile_info{class_definition = #class{name = ThisClass}}, Line) ->
    {clause, Line,
        [{tuple, Line, [{atom, Line, copy}, {var, Line, 'Pid'}, {var, Line, 'Ref'}]}],
        [],
        [send_result({ok, {var, Line, 'This'}}, Line)
        ,continue_loop(ThisClass, Line)
        ]
    }.


%% Function: gen_delete(_, Line)
%% Purpose: Creates a clause for rserver/2, that handles the delete-commands sent to the object.
%%          If a delete message is recieved, then the loop will terminate.
%% Arguments: see gen_rserver/2
%% Processed message: 'delete'
%% Response: none
gen_delete(_, Line) ->
    {clause, Line,
        [{atom, Line, delete}],
        [],
        [{atom, Line, ok}]
    }.

%% Function: gen_update_clause(CompileInfo, Line)
%% Purpose: Creates a clause for rserver/2, that handles the update_call-commands sent to the object.
%%          A method of the loop-variable object is called, and its result is interpreted as 
%%          having a new object instance inside it.
%%            Either: {NewObj, Val}
%%                or: NewObj
%%          NewObj is type-checked in both cases, and it becomes the new loop-variable for the object.
%%          In the first case, Val is sent back to the caller.
%% Arguments: see gen_rserver/2
%% Returns: the AST fragment that handles update_call-commands in the recieve statement
gen_update_call(#compile_info{class_definition = Def = #class{name = ThisClass}}, Line) ->
    {clause, Line,
        [{tuple, Line, 
            [{atom, Line, update_call}, {var, Line, 'Method'}, {var, Line, 'Args'}, {var, Line, 'Pid'}, {var, Line, 'Ref'}]
         }
        ],
        [],
        [{'case', Line, ast:dmcall('This', 'Method', 'Args', Line),
            %% switch by the pattern of the return value
            [
                {clause, Line,
                    %% 2-sized tuple, at the first position there is the new state
                    %%                at the second position there is the result
                    [{tuple, Line, [{var, Line, 'This2'}, {var, Line, 'Result'}]}],
                    [[ast:class_typecheck_guard(Def, {var, Line, 'This2'}, Line)]],
                    [send_result({ok, {var, Line, 'Result'}}, Line)
                    ,update_loop(ThisClass, {var, Line, 'This2'}, Line)
                    ]
                },
                {clause, Line,
                    %% a single value, which is by the way a valid object instance
                    [{var, Line, 'This2'}],
                    [[ast:class_typecheck_guard(Def, {var, Line, 'This2'}, Line)]],
                    [send_result(none, Line)
                    ,update_loop(ThisClass, {var, Line, 'This2'}, Line)
                    ]
                }
            ]
         }
        ]
    }.

%% Function: gen_query_clause(CompileInfo, Line)
%% Purpose: Creates a clause for rserver/2, that handles the query_call-commands sent to the object.
%%          A method of the loop-variable object is called, and its result is returned to the caller.
%%          The internal object stored as the loop-variable is not affected.
%% Arguments: see gen_rserver/2
%% Returns: the AST fragment that handles query_call-commands in the recieve statement
gen_query_call(#compile_info{class_definition = #class{name = ThisClass}}, Line) ->
    {clause, Line,
        [{tuple, Line, 
            [{atom, Line, query_call}, {var, Line, 'Method'}, {var, Line, 'Args'}, {var, Line, 'Pid'}, {var, Line, 'Ref'}]
         }
        ],
        [],
        [send_result({ok, ast:dmcall('This', 'Method', 'Args', Line)}, Line)
        ,continue_loop(ThisClass, Line)
        ]
    }.



%% Function: getset_clauses(RecDef, ClassInfo, Pos, Line)
%% Purpose:  Creates a clauses for rserver/2, that handle the get/set-commands sent to the object.
%% Arguments: see gen_rserver/2
getset_clauses(#class{fieldmap = FieldMap} = Class, Line) ->
    getset_clauses(FieldMap, Class, Line).
getset_clauses([#class_field{access = public, name = Name} = FD|Fs], ClassDef, Line) ->
    [get_clause(Name, ClassDef, Line)
    ,set_clause(FD, ClassDef, Line)
    |getset_clauses(Fs, ClassDef, Line)
    ];
getset_clauses([_|Fs], ClassDef, Line) ->
    getset_clauses(Fs, ClassDef, Line);
getset_clauses([], _, _) -> [].


%% Function: set_clause(Field, ClassDef, Line)
%%             creates a chunk of code which...
%%             -is a clause of the main receive-loop (created in gen_rserver/2...),
%%             -receives a message in the form of...
%%                  {set, FieldName, Value} and handles it.
%%             In other words, this function generates a setting-clause for a single
%%             specific field, specified by the argument Field.
%% Arguments:
%%    Field::#class_field{} - field of class to create a set clause
%%    ClassDef::#class{} - definition of the class, for which this clause is beeing generated
%%    Line::integer() - line number to use in AST nodes of the source code
%% Returns:
%%   the AST of the clause
set_clause(#class_field{name = Name, global_pos = GlobalPos, local_pos = LocalPos}, #class{name = ClassName}, Line) ->
    {clause, Line,
        [{tuple, Line,
            [{atom, Line, set}, {atom, Line, Name}, {var, Line, 'Val'}]
        }],
        [],
        [{match, Line,
            {var, Line, 'This2'},
            ast:setelement(
                GlobalPos,
                {var, Line, 'This'},
                ast:setelement(
                    LocalPos,
                    ast:element(
                        GlobalPos,
                        {var, Line, 'This'},
                        Line),
                    {var, Line, 'Val'},
                    Line),
                Line)
         },
         %send_result(none, Line),
         update_loop(ClassName, {var, Line, 'This2'}, Line)
        ]
    }.

%% Function: get_clause(Name, ClassDef, Line)
%% Purpose:
%%      creates a chunk of code which...
%%             -is a clause of the main receive-loop (created in gen_rserver/2...),
%%             -receives a message in the form of...
%%                  {get, FieldName, Pid, Ref} and handles it, where
%%                  Pid is the process id of the caller, to whom the field value is
%%                  sent back.
%%
%%             More precisely, this function generates a single clause 
%%             for a specific field.
%% Arguments:
%%    Name: the name of the field for which this clause is generated
%%    ClassDef::#class{} - definition of the class, for which this clause is beeing generated
%%    Line::integer() - line number to use in AST nodes of the source code
%% Returns:
%%    The AST of the clause.
get_clause(Name, ClassDef = #class{name = ClassName}, Line) ->
    {clause, Line,
        [{tuple, Line,
            [{atom, Line, get}, {atom, Line, Name}, {var, Line, 'Pid'}, {var, Line, 'Ref'}]}],
        [],
        [send_result({ok, classes:field_accessor(ClassDef, Name, {var, Line, 'This'}, none, Line)}, Line)
        ,continue_loop(ClassName, Line)
        ]
    }.
